/**
 * 适配data的数据展示
 * data: object | function 需要适配的数据
 * return: object 适配后的数据
*/
function adapt_data(data) {
    if (typeof data === "function") {
        return data();
    } else {
        return data;
    }
}
/**
 * v-model双向数据绑定原理
 * node: HTMLElement 需要双向数据绑定的元素
*/
function v_model_principle (node) {
    try {
        // 1. 判断元素节点上是否有“v-model”属性
        const vmKey = node.getAttribute("v-model");
        // 2. 获取相应变量
        if (vmKey) {
            const target = this[vmKey];
            // 3. 把变量赋值到相应的元素上
            node.value = target;
            // 4. 添加input事件，把Vue上相应变量修改
            node.addEventListener("input", (event) => {
                this[vmKey] = event.target.value;
            })
        }
    } catch(e) {
        console.log("Error:" + e);
    }
}
/**
 * 模板解析
 * dom: string 需要解析的模板
 * return: Document 解析后的dom
*/
function template_analysis(dom) {
    const childNodes = dom.childNodes;
    childNodes.forEach(item => {
        // 判断节点
        // 如果是文本节点就替换文本从“data”中取数据
        if (item.nodeType === 3) {
            // 正则表达式匹配对映的值
            let reg = /\{\{(.*?)\}\}/;
            let text = item.textContent;
            // 替换成相应的变量
            item.textContent = text.replace(reg, (match, vmKey) => {
                vmKey = vmKey.trim();
                const watch = new Watch(this,  vmKey, item, "textContent");
                // 判断监视对象中是否已经有这个监控项
                if (this.$watchEvent[vmKey]) {
                    this.$watchEvent[vmKey].push(watch);
                } else {
                    this.$watchEvent[vmKey] = [watch];
                }
                return this.$data[vmKey];
            })

        } else if (item.nodeType === 1) {
            // 检查元素节点上是否有事件
            const methodKey = item.getAttribute('@click')
            if (methodKey) {
                // 拿到对应方法
                const method = this.methods[methodKey].bind(this);
                item.addEventListener("click", method);
            }
            // v-model双向数据绑定原理
            v_model_principle.call(this, item);
            // 如果是正常的节点就递归调用
            template_analysis.call(this, item);
        }
    })
}
/**
 * 数据劫持，为了让data中的数据在Vue大对象上也可以访问
*/
function proxyData() {
    for (let key in this.$data) {
        Object.defineProperty(this, key, {
            get() {
                return this.$data[key];
            },
            set(value) {
                this.$data[key] = value;
            }
        })
    }
}

class Vue {
    constructor(options) {
        const { data, el, methods } = options;
        /**
         * 初始化阶段
         * */
        // 创建监视对象
        this.$watchEvent = {};
        // 1. 把data绑定到this上
        this.$data = adapt_data(data);
        // 2. 把el绑定到this上
        this.$el = document.querySelector(el);
        // 3. 把方法绑定到this上
        this.methods = methods;
        // 4. 添加数据劫持
        proxyData.call(this);
        // 5. 发布订阅更新数据
        this.observe();
        // 6. 模板解析
        template_analysis.call(this, this.$el);
        
    }
    /**
     * 发布订阅更新数据
     * */ 
    observe () {
        for (let key in this.$data){
            let value = this.$data[key];
            const that = this;
            Object.defineProperty(this.$data, key, {
                get () {
                    return value;
                },
                set (val) {
                    value = val;
                    // 执行监视对象中的监视器的update方法
                    let target = that.$watchEvent[key];
                    if (target) {
                        target.forEach(item => {
                            item.update();
                        })
                    }

                }
            })
        }
    }
}
/**
 * 监视器对象·
*/
class Watch {
    constructor (vm, key, node, attr) {
        // vue实例
        this.vm = vm;
        // 要修改的变量名
        this.key = key;
        // 要修改的节点
        this.node = node;
        // 要修改的属性
        this.attr = attr;
    }
    /**
     * 更新节点方法
    */
   update () {
    this.node[this.attr] = this.vm[this.key];
   }
}


